/*

This file is part of roomyak.

Copyright (C) 2008 Pixecur Technologies Incorporated (Esotera).
Visit Esotera at http://www.esoteras3.com
 
roomyak is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
 
roomyak is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.
 
You should have received a copy of the GNU General Public License
along with roomyak; see the file COPYING.  If not, write to 
Pixecur Technologies, 213-31 Peet Street, St. John's, NL, A1B 3W8. 
 
Linking this library statically or dynamically with other modules is
making a combined work based on this library.  Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.
 
*/
package com.datasentinel.util;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * 
 * This type offers convenience methods for calculating various kinds of
 * checksums on files, strings, byte arrays, and input streams.
 * 
 * This type supports the same algorithms that are supported by
 * java.security.MessageDigest since this how checksums are
 * calculated.
 * 
 * @author rickp
 * 
 */
public class DigestUtils
{
	private MessageDigest digest;
	private byte[] streamReadBuffer;

	public DigestUtils(String algorithm) throws NoSuchAlgorithmException
	{
		streamReadBuffer = new byte[8192];
		digest = MessageDigest.getInstance(algorithm);
	}
	
	public BigInteger sum(File file) throws FileNotFoundException
	{
		return sumAndThenCloseInternalInputStream(new FileInputStream(file));
	}
	
	public BigInteger sum(byte[] bytes)
	{
		return sumAndThenCloseInternalInputStream(new ByteArrayInputStream(bytes));
	}

	public byte[] sumBytes(byte[] bytes)
	{
		return sumBytesAndThenCloseInternalInputStream(new ByteArrayInputStream(bytes));
	}
	
	public BigInteger sum(String str)
	{
		return sum(str.getBytes());
	}

	public BigInteger sum(InputStream is) throws IOException
	{
		return toBigInteger(calculateChecksum(is));
	}

	public byte[] sumBytes(InputStream is) throws IOException
	{
		return calculateChecksum(is);
	}

	
	private byte[] calculateChecksum(InputStream is) throws IOException
	{
		digest.reset();
		int numBytesActuallyReadFromStream = 0;
		while (-1 != (numBytesActuallyReadFromStream = is.read(streamReadBuffer)))
		{
			digest.update(streamReadBuffer, 0, numBytesActuallyReadFromStream);
		}
		return digest.digest();
	}
	
	
	
	
	private <T extends InputStream> BigInteger sumAndThenCloseInternalInputStream(
			T inputStream)
	{
		if (inputStream == null)
			throw new IllegalArgumentException("argument cannot be null");
		BigInteger sum = null;
		try
		{
			sum = sum(inputStream);
		}
		catch (Exception e)
		{
			throw new RuntimeException(e);
		}
		finally
		{
			try
			{
				inputStream.close();
			}
			catch (IOException e)
			{
			}
		}
		return sum;
	}

	private <T extends InputStream> byte[] sumBytesAndThenCloseInternalInputStream(
			T inputStream)
	{
		if (inputStream == null)
			throw new IllegalArgumentException("argument cannot be null");
		byte[] sum = null;
		try
		{
			sum = sumBytes(inputStream);
		}
		catch (Exception e)
		{
			throw new RuntimeException(e);
		}
		finally
		{
			try
			{
				inputStream.close();
			}
			catch (IOException e)
			{
			}
		}
		return sum;
	}

	
	@SuppressWarnings("serial")
	private static BigInteger toBigInteger(byte[] checksum)
	{
		return new BigInteger(1, checksum) {
			/*
			 * output a zero-padded 32 character hexadecimal string to match
			 * that of popular Linux utilities like md5sum
			 */
			@Override
			public String toString()
			{
				return String.format("%032x", this);
			}
		};
	}
}
